package org.emet.jee.core.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.emet.jee.core.dao.arch.CurrentArchHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MultiDataSource implements DataSource {
	protected boolean defaultAutoCommit;
	protected Boolean defaultReadOnly;
	protected int defaultTransactionIsolation;
	protected String defaultCatalog;
	protected String driverClassName;
	protected int maxActive;
	protected int maxIdle;
	protected int minIdle;
	protected int initialSize;
	protected long maxWait;
	protected boolean poolPreparedStatements;
	protected int maxOpenPreparedStatements;
	protected boolean testOnBorrow;
	protected boolean testOnReturn;
	protected long timeBetweenEvictionRunsMillis;
	protected int numTestsPerEvictionRun;
	protected long minEvictableIdleTimeMillis;
	protected boolean testWhileIdle;
	protected String password;
	protected String url;
	protected String username;
	protected String validationQuery;
	private boolean accessToUnderlyingConnectionAllowed;
	private boolean restartNeeded;
	protected GenericObjectPool connectionPool;
	protected Properties connectionProperties;
	protected DataSource dataSource;
	protected PrintWriter logWriter;
	private Map<Long, DataSource> dsMap = new HashMap<Long, DataSource>();
	private static Logger logger = LoggerFactory.getLogger(MultiDataSource.class);

	public PrintWriter getLogWriter() throws SQLException {
		return getDataSource().getLogWriter();
	}

	private DataSource getDataSource() {
		Long key = CurrentArchHolder.get();
		if (key == null) {
			key = 0L;
		}
		DataSource dataSource = dsMap.get(key);

		if (null == dataSource) {
			synchronized (key.toString().intern()) {
				if (null == dataSource) {
					BasicDataSource bs = createDS(driverClassName, url, username, password, initialSize, maxActive,
							maxIdle, maxWait, poolPreparedStatements, defaultAutoCommit);
					dsMap.put(key, bs);
					logger.info("New datasource {}", key);
				}
				dataSource = dsMap.get(key);
			}
		}
		return dataSource;
	}

	private BasicDataSource createDS(String driverClassName, String url, String username, String password,
			int initialSize, int maxActive, int maxIdle, long maxWait, boolean poolPreparedStatements,
			boolean defaultAutoCommit) {
		BasicDataSource bs = new BasicDataSource();
		bs.setDriverClassName(driverClassName);
		bs.setUrl(url);
		bs.setUsername(username);
		bs.setPassword(password);
		bs.setInitialSize(initialSize);
		bs.setMaxActive(maxActive);
		bs.setMaxIdle(maxIdle);
		bs.setMaxWait(maxWait);
		bs.setPoolPreparedStatements(poolPreparedStatements);
		bs.setDefaultAutoCommit(defaultAutoCommit);
		return bs;
	}

	public void setLogWriter(PrintWriter out) throws SQLException {
		getDataSource().setLogWriter(out);
	}

	public void setLoginTimeout(int seconds) throws SQLException {
		getDataSource().setLoginTimeout(seconds);
	}

	public int getLoginTimeout() throws SQLException {
		return getDataSource().getLoginTimeout();
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return getDataSource().unwrap(iface);
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return getDataSource().isWrapperFor(iface);
	}

	public Connection getConnection() throws SQLException {
		return getDataSource().getConnection();
	}

	public Connection getConnection(String username, String password) throws SQLException {
		return getDataSource().getConnection(username, password);
	}

	public boolean isDefaultAutoCommit() {
		return defaultAutoCommit;
	}

	public void setDefaultAutoCommit(boolean defaultAutoCommit) {
		this.defaultAutoCommit = defaultAutoCommit;
	}

	public Boolean getDefaultReadOnly() {
		return defaultReadOnly;
	}

	public void setDefaultReadOnly(Boolean defaultReadOnly) {
		this.defaultReadOnly = defaultReadOnly;
	}

	public int getDefaultTransactionIsolation() {
		return defaultTransactionIsolation;
	}

	public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
		this.defaultTransactionIsolation = defaultTransactionIsolation;
	}

	public String getDefaultCatalog() {
		return defaultCatalog;
	}

	public void setDefaultCatalog(String defaultCatalog) {
		this.defaultCatalog = defaultCatalog;
	}

	public String getDriverClassName() {
		return driverClassName;
	}

	public void setDriverClassName(String driverClassName) {
		this.driverClassName = driverClassName;
	}

	public int getMaxActive() {
		return maxActive;
	}

	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}

	public int getMaxIdle() {
		return maxIdle;
	}

	public void setMaxIdle(int maxIdle) {
		this.maxIdle = maxIdle;
	}

	public int getMinIdle() {
		return minIdle;
	}

	public void setMinIdle(int minIdle) {
		this.minIdle = minIdle;
	}

	public int getInitialSize() {
		return initialSize;
	}

	public void setInitialSize(int initialSize) {
		this.initialSize = initialSize;
	}

	public long getMaxWait() {
		return maxWait;
	}

	public void setMaxWait(long maxWait) {
		this.maxWait = maxWait;
	}

	public boolean isPoolPreparedStatements() {
		return poolPreparedStatements;
	}

	public void setPoolPreparedStatements(boolean poolPreparedStatements) {
		this.poolPreparedStatements = poolPreparedStatements;
	}

	public int getMaxOpenPreparedStatements() {
		return maxOpenPreparedStatements;
	}

	public void setMaxOpenPreparedStatements(int maxOpenPreparedStatements) {
		this.maxOpenPreparedStatements = maxOpenPreparedStatements;
	}

	public boolean isTestOnBorrow() {
		return testOnBorrow;
	}

	public void setTestOnBorrow(boolean testOnBorrow) {
		this.testOnBorrow = testOnBorrow;
	}

	public boolean isTestOnReturn() {
		return testOnReturn;
	}

	public void setTestOnReturn(boolean testOnReturn) {
		this.testOnReturn = testOnReturn;
	}

	public long getTimeBetweenEvictionRunsMillis() {
		return timeBetweenEvictionRunsMillis;
	}

	public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
		this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
	}

	public int getNumTestsPerEvictionRun() {
		return numTestsPerEvictionRun;
	}

	public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
		this.numTestsPerEvictionRun = numTestsPerEvictionRun;
	}

	public long getMinEvictableIdleTimeMillis() {
		return minEvictableIdleTimeMillis;
	}

	public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
		this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
	}

	public boolean isTestWhileIdle() {
		return testWhileIdle;
	}

	public void setTestWhileIdle(boolean testWhileIdle) {
		this.testWhileIdle = testWhileIdle;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getValidationQuery() {
		return validationQuery;
	}

	public void setValidationQuery(String validationQuery) {
		this.validationQuery = validationQuery;
	}

	public boolean isAccessToUnderlyingConnectionAllowed() {
		return accessToUnderlyingConnectionAllowed;
	}

	public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) {
		this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
	}

	public boolean isRestartNeeded() {
		return restartNeeded;
	}

	public void setRestartNeeded(boolean restartNeeded) {
		this.restartNeeded = restartNeeded;
	}

	public GenericObjectPool getConnectionPool() {
		return connectionPool;
	}

	public void setConnectionPool(GenericObjectPool connectionPool) {
		this.connectionPool = connectionPool;
	}

	public Properties getConnectionProperties() {
		return connectionProperties;
	}

	public void setConnectionProperties(Properties connectionProperties) {
		this.connectionProperties = connectionProperties;
	}

	public Map<Long, DataSource> getDsMap() {
		return dsMap;
	}

	public void setDsMap(Map<Long, DataSource> dsMap) {
		this.dsMap = dsMap;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

}
